import uuid
import threading as th
import datetime as dt
import MySQLdb as mdb
import numpy

def str_uuid(x):
    '''convert uuid string to uuid'''
    return uuid.UUID(hex = x)

LastUpdateTime = {}
Locals = th.local()

def isStaleUpdate(oid, when):
    pass

def setLastUpdate(oids):
    pass

class DBConnection:
    __connectParams__ = dict(db = "GSN", user = "GSNUser", password = "GSNPassword")
    def __init__ (self, **params):
        if not params:
            params = DBConnection.__connectParams__
        self.connection = mdb.connect(**params);
    
    def deleteBy(self,table,oid):
        pass
    def insert(self,table,oid,dataDict):
        pass
    def update(self,table,oid,dataDict):
        pass
    def fetchByOid(self,table,oid):
        pass
    def equalityFind(self, table, **pattern):
        pass

    
#there is one Transaction per thread and it owns a database connection
class Transaction:
    def __init__(self, name):
        self.name = name
        self.connection = connection
        self.knownObjects = {}
        self.startingValues = {}
        self.deletedIds = set()
        
    @staticmethod    
    def current(self):  #gets it from threadlocal storage, creates if missing
        try:
            txn = Local.transaction
        except Exception:
            Local.transaction = Transaction(th.currentThread().getName())

    def cleanup(self):
        self.knownObjects.clear()
        self.startingValues.clear()
        self.deletedIds.clear()
    
    def commit(self):
        try:
            self.beginTransaction()
            updates = []
            for pair in self.deletedIds:
                oid = pair[0]
                self.knownObjects.pop(oid, None)
                self.startingValues.pop(oid, None)
                self.connection.delete(pair[1], oid)
            for key,value in self.knownObjects.iteritems():
                table = value.__class__.__table__
                if value.isNew:
                    self.connection.insert(table,key,value.valueDict)
                else:
                    startingValues,when = self.startingValues[key]
                    diff = value.updateDifferences(startingValues)
                    if diff:
                        if isStaleUpdate(key, when):
                            raise RuntimeError("modified object %s from stale snapshot")
                        else:
                            self.connection.update(table,key,diff)
            self.connection.commitTranastion()
            date = dt.datetime.now()
            for oid in updates:
                LastUpdateTime[oid] = date
        except Exception as ex:
            print "Aborting txn %s due to %s", (self.name, ex)
            self.abortTransaction()
        finally:
            self.cleanup()
        
    
    def abort(self):
        self.cleanup()
    
    
    def noteDelete(self, object):
        self.deletedIds.add((object.oid, object.__table__))
        
    def noteObject(self, object):
        self.knownObjects[object.oid] = object
        if not object.isNew:
            self.startingValues[object.oid] = (object.valueDict(), dt.datetime.now())
        
class PersistentObject:
    known_objects = {}
    @classmethod
    def instantiate(cls,id,data):
        data["isNew"] = False
        object = cls(**data)
        object.handleFetch()
        return object

    #make sure I am properly initialized with an ID and tracked by the txn
    def __init__(self, oid):
        if not oid:
            oid = uuid.uuid4()
        self.oid = oid
        Transaction.current.noteObject(self)

    #return my values keyed by attribute names for my persistent fields
    def valueDict(self):
        #dictionary of object values for keys defined by __attributes__
        return dict(oid = oid.int) #subclasses must add their entries
        
    #do whatever is necessary to persist changes to me
    def save(self):
        if self.is_new:
            self.insert()
        else:
            self.update()

    #do anything additional my class needs on object fetch
    def handleFetch():
        pass

    #do what is needed to insert me using write connection
    def insert(self):
        pass

    #do what is needed to update me.  I know how to tell what has changed
    def update(self):
        pass

    #do what is needed to fully delete me
    def delete(self):
        pass

    @classmethod
    #find and return all instances matching matching the attribute values
    def find(cls, **keys_values):
        pass

    @classmethod
    #find and return the instance (if any) that has the given object id
    def findById(cls, oid):
        pass
        
#These type classes are not used for now.  Experimental code toward a more generic persistence
class DBType():
    def databaseValue(self,memoryValue):
        return memoryValue
    def memoryValue(self,databaseValue):
        return databaseValue
    def dbType(self):
        pass
    
class SimpleType(DBType):
    pass

class IntType(SimpleType):
    def dbType():
        return "int"

class FloatType(SimpleType):
    def dbType():
        return "float"

class OidType(SimpleType):
    def dbType():
        return "int(128) unsigned"
    def databaseValue(memoryValue):
        return uuid_int(memoryValue)
    def memoryValue(dbValue):
        return int_uuid(dbValue)
    

class ArrayType():
    __arrayTables__ = {}
    @staticmethod
    def tableHolding(aType):
        tableName = None
        if not __arrayTables__.has_key(aType.dbType):
            tableName = "Array%s" % (aType.dbType)
            command = "create table %s (owner = int(128) not null Primary Key, name varchar(256), index = int, value = %s)" % (tableName, aType.dbType)
            try:
                Connection.current().execute(command)
                __arrayTables__[aType.dbType] = tableName
            except Exception as ex:
                print "Table creation failed with %s on commmand %s" % (ex, command)
                raise ex
        else:
            tableName = __arrayTables__[aType.dbType]
        return tableName
    
    def __init__(self, owner, attribute, contentType):
        self.table = tableHolding(contentType)
        self.contentType = type
        self.owner = owner
        self.value = []

    def handleSave(self, value):
        pass
    
